package cn.cheniy.swiftmark.common.security.filter;

import cn.cheniy.swiftmark.api.auth.bo.UserInfoInTokenBO;
import cn.cheniy.swiftmark.api.auth.constant.SysTypeEnum;
import cn.cheniy.swiftmark.api.auth.feign.TokenFeignClient;
import cn.cheniy.swiftmark.api.rbac.constant.HttpMethodEnum;
import cn.cheniy.swiftmark.api.rbac.feign.PermissionFeignClient;
import cn.cheniy.swiftmark.common.constant.Auth;
import cn.cheniy.swiftmark.common.feign.FeignInsideAuthConfig;
import cn.cheniy.swiftmark.common.handler.HttpHandler;
import cn.cheniy.swiftmark.common.response.ResponseEnum;
import cn.cheniy.swiftmark.common.response.ServerResponseEntity;
import cn.cheniy.swiftmark.common.security.AuthUserContext;
import cn.cheniy.swiftmark.common.security.adapter.AuthConfigAdapter;
import cn.cheniy.swiftmark.common.util.IpHelper;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
 * @author cheniy
 * @description 授权过滤，只要实现AuthConfigAdapter接口，添加对应路径即可：
 *  
 */
@Component
public class AuthFilter implements Filter {

    private static Logger logger = LoggerFactory.getLogger(AuthFilter.class);

    @Autowired
    private AuthConfigAdapter authConfigAdapter;

    @Autowired
    private HttpHandler httpHandler;

    @Autowired
    private TokenFeignClient tokenFeignClient;

    @Autowired
    private PermissionFeignClient permissionFeignClient;

    @Autowired
    private FeignInsideAuthConfig feignInsideAuthConfig;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        logger.info("请求接收前拦截器，过滤...");
        // 将 ServletRequest 和 ServletResponse
        // 转换为 HttpServletRequest 和 HttpServletResponse，以便处理 HTTP 请求和响应
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 1.调用 feignRequestCheck 方法进行 Feign 请求校验，如果校验失败则返回未授权响应
        if (!feignRequestCheck(req)) {
            httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
            return;
        }

        // 如果请求的 URI 是检查 Token 的 URI，则直接放行
        if (Auth.CHECK_TOKEN_URI.equals(req.getRequestURI())) {
            chain.doFilter(req, resp);
            return;
        }

        // 2. 检查请求的 URI 是否匹配不需要授权的路径，如果匹配则直接放行
        List<String> excludePathPatterns = authConfigAdapter.excludePathPatterns();

        // 如果匹配不需要授权的路径，就不需要校验是否需要授权
        if (CollectionUtil.isNotEmpty(excludePathPatterns)) {
            for (String excludePathPattern : excludePathPatterns) {
                AntPathMatcher pathMatcher = new AntPathMatcher();
                if (pathMatcher.match(excludePathPattern, req.getRequestURI())) {
                    chain.doFilter(req, resp);
                    return;
                }
            }
        }

        // 从请求头中获取 Authorization 字段的 accessToken，如果为空则返回未授权响应。
        String accessToken = req.getHeader("Authorization");
        if (StrUtil.isBlank(accessToken)) {
            httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
            return;
        }

        // 校验token，并返回用户信息
        // 3. 调用 tokenFeignClient 进行 Token 校验，如果校验失败则返回未授权响应。
        ServerResponseEntity<UserInfoInTokenBO> userInfoInTokenVoServerResponseEntity = tokenFeignClient
                .checkToken(accessToken);
        if (!userInfoInTokenVoServerResponseEntity.isSuccess()) {
            httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
            return;
        }

        UserInfoInTokenBO userInfoInToken = userInfoInTokenVoServerResponseEntity.getData();
        // 4. 调用 checkRbac 方法进行角色权限校验，如果校验失败则返回未授权响应。
        // 需要用户角色权限，就去根据用户角色权限判断是否
        if (!checkRbac(userInfoInToken,req.getRequestURI(), req.getMethod())) {
            httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED));
            return;
        }

        try {
            // 5. 保存上下文
            AuthUserContext.set(userInfoInToken);

            chain.doFilter(req, resp);
        }
        finally {
            // 在请求处理完成后，清理 AuthUserContext 中的用户信息。
            AuthUserContext.clean();
        }

    }

    private boolean feignRequestCheck(HttpServletRequest req) {
        // 不是feign请求，不用校验
        // 检查请求的 URI 是否以 Feign 内部请求的前缀开头，如果不是则直接返回 true。
        if (!req.getRequestURI().startsWith(FeignInsideAuthConfig.FEIGN_INSIDE_URL_PREFIX)) {
            return true;
        }

        // 从请求头中获取 Feign 内部请求的密钥，如果为空或与配置的密钥不匹配则返回 false。
        // 校验feign 请求携带的key 和 value是否正确
        String feignInsideSecret = req.getHeader(feignInsideAuthConfig.getKey());
        if (StrUtil.isBlank(feignInsideSecret) || !Objects.equals(feignInsideSecret,feignInsideAuthConfig.getSecret())) {
            return false;
        }
        // 获取配置的 IP 白名单，移除空 IP 地址。
        List<String> ips = feignInsideAuthConfig.getIps();
        // 移除无用的空ip
        ips.removeIf(StrUtil::isBlank);
        // 有ip白名单，且ip不在白名单内，校验失败
        if (CollectionUtil.isNotEmpty(ips)
                && !ips.contains(IpHelper.getIpAddr())) {
            logger.error("ip not in ip White list: {}, ip, {}", ips, IpHelper.getIpAddr());
            return false;
        }
        return true;
    }

    /**
     * 用户角色权限校验
     * @param uri 请求路径uri
     * @return 是否校验成功
     */
    public boolean checkRbac(UserInfoInTokenBO userInfoInToken, String uri, String method) {
        // 检查用户的系统类型是否为 PLATFORM【平台端】 或 MULTISHOP【商家端】，如果不是则直接返回 true。
        if (!Objects.equals(SysTypeEnum.PLATFORM.value(), userInfoInToken.getSysType()) && !Objects.equals(SysTypeEnum.MULTISHOP.value(), userInfoInToken.getSysType())) {
            return true;
        }
        // 调用 permissionFeignClient 进行角色权限校验，传递用户 ID、系统类型、请求 URI、是否为管理员和 HTTP 方法等参数。
        ServerResponseEntity<Boolean> booleanServerResponseEntity = permissionFeignClient
                .checkPermission(userInfoInToken.getUserId(), userInfoInToken.getSysType(),uri,userInfoInToken.getIsAdmin(), HttpMethodEnum.valueOf(method.toUpperCase()).value() );

        // 如果校验结果不成功，则返回 false。
        if (!booleanServerResponseEntity.isSuccess()) {
            return false;
        }

        return booleanServerResponseEntity.getData();
    }

}
